package cn.hbads.version.domain.calendar.factory;

import cn.hbads.support.compare.CompareUtil;
import cn.hbads.support.error.ErrorType;
import cn.hbads.support.id.IdUtil;
import cn.hbads.support.json.JsonUtil;
import cn.hbads.support.structure.result.ComputeResult;
import cn.hbads.support.structure.tuple.Tuple;
import cn.hbads.version.config.DefaultVersionStrategyConfigProvider;
import cn.hbads.version.config.VersionStrategyConfigProvider;
import cn.hbads.version.domain.calendar.aggregate.FieldVersionCalendar;
import cn.hbads.version.domain.calendar.aggregate.VersionCalendar;
import cn.hbads.version.domain.version.aggregate.DataVersion;
import cn.hbads.version.domain.version.aggregate.DataVersionChain;
import cn.hbads.version.domain.version.model.BusinessModel;
import cn.hbads.version.domain.version.model.VersionType;
import cn.hbads.version.error.VersionErrorType;
import cn.hbads.version.parser.BusinessModelParser;
import cn.hbads.version.parser.model.FieldVersionModel;
import cn.hbads.version.parser.model.ListVersionModel;
import cn.hbads.version.parser.model.VersionModel;
import cn.hbads.version.provider.DynNameProvider;
import cn.hbads.version.provider.MessageProvider;
import cn.hbads.version.provider.ValueTranslator;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import org.apache.commons.lang3.StringUtils;
import org.javers.common.collections.Sets;
import org.javers.core.diff.Change;
import org.javers.core.diff.Diff;
import org.javers.core.diff.changetype.ValueChange;
import org.javers.core.diff.changetype.container.ListChange;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.InvocationTargetException;
import java.time.LocalDate;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <b> 版本日历工厂方法，从变化的数据版本链中生成实际变化点处的版本日历 </b>
 *
 * @author : yvvb
 * @date : 2025/5/5
 */
@Component
public class VersionCalendarFactory {
    private final VersionStrategyConfigProvider versionStrategyProvider;

    public VersionCalendarFactory(ObjectProvider<VersionStrategyConfigProvider> versionStrategyConfigProvider) {
        this.versionStrategyProvider = versionStrategyConfigProvider.getIfAvailable(DefaultVersionStrategyConfigProvider.INSTANCE_SUPPLIER);
    }

    private static <T, E extends DataVersion<T>> VersionCalendar generateDeleteVersionCalender(E deletedVersion) {
        return VersionCalendar.<T>builder()
                .versionCalendarId(IdUtil.snowId())
                .dataId(deletedVersion.getDataId().toString())
                .dataVersionDate(deletedVersion.getVersionDate())
                .dataType(deletedVersion.getDataType())
                .dataName(deletedVersion.getDataName())
                .versionType(VersionType.DELETE)
                .beforeJson(JsonUtil.toJson(deletedVersion.toBusinessModel()).orElse(null))
                .recordMessage(deletedVersion.getDeletedMsg())
                .build();
    }

    private static <T, E extends DataVersion<T>> VersionCalendar generateCreateVersionCalender(E newVersion) {
        return VersionCalendar.<T>builder()
                .versionCalendarId(IdUtil.snowId())
                .dataId(newVersion.getDataId().toString())
                .dataVersionDate(newVersion.getVersionDate())
                .dataType(newVersion.getDataType())
                .dataName(newVersion.getDataName())
                .versionType(VersionType.CREATE)
                .afterJson(JsonUtil.toJson(newVersion.toBusinessModel()).orElse(null))
                .recordMessage(newVersion.getCreatedMsg())
                .build();

    }

    private static <T> VersionCalendar buildNoChangeCalendar(T dataId, LocalDate modifyDate, Long versionCalenderId, BusinessModel after, BusinessModel before) {
        return VersionCalendar.<T>builder()
                .versionCalendarId(versionCalenderId)
                .dataId(dataId.toString())
                .dataName(after.getDataName())
                .dataType(after.getDataType())
                .versionType(VersionType.NO_CHANGE)
                .fieldVersionCalendars(List.of())
                .beforeJson(JsonUtil.toJson(before).orElse(null))
                .afterJson(JsonUtil.toJson(after).orElse(null))
                .dataVersionDate(modifyDate)
                .recordMessage("无变化")
                .build();
    }

    private static String formatRecordMessage(String format, String showName, String beforeStr, String afterStr) {

        return format
                .replace("{name}", showName)
                .replace("{before}", beforeStr)
                .replace("{after}", afterStr);
    }

    private static List<Object> getListDiff(List<Object> list1, List<Object> list2) {
        final List<Object> resultList = new ArrayList<>();
        final var countingMap = new HashMap<Object, Integer>();
        list1.forEach(r ->
                countingMap.compute(r, (key, count) ->
                        (count == null) ? 1 : count + 1
                )
        );
        list2.forEach(r ->
                countingMap.computeIfPresent(r, (key, count) ->
                        (count > 1) ? count - 1 : 0
                )
        );
        countingMap.forEach((k, v) -> {
            while (v > 0) {
                resultList.add(k);
                v--;
            }
        });
        return resultList;
    }

    public static boolean detectListOrderIsMoved(List<Object> beforeList, List<Object> afterList) {
        Set<Object> beforeSet = new HashSet<>(beforeList);
        Set<Object> afterSet = new HashSet<>(afterList);
        Set<Object> innerSet = Sets.intersection(beforeSet, afterSet);
        int i = 0;
        int j = 0;
        while (i < beforeList.size() && j < afterList.size()) {
            while (i < beforeList.size() && !innerSet.contains(beforeList.get(i))) {
                i++;
            }
            while (j < afterList.size() && !innerSet.contains(afterList.get(j))) {
                j++;
            }
            if (i == beforeList.size() || j == afterList.size()) {
                break;
            }
            if (!beforeList.get(i).equals(afterList.get(j))) {
                return true;
            } else {
                i++;
                j++;
            }
        }
        return false;
    }

    public <T, E extends DataVersion<T>> ComputeResult<Map<LocalDate, VersionCalendar>, ErrorType> batchCreateFromVersionChain(DataVersionChain<T, E> versionChain) {
        Collection<E> changedVersions = versionChain.getChangedVersions();
        if (CollUtil.isEmpty(changedVersions)) {
            return ComputeResult.success(Map.of());
        }
        // 0. 按版本类型分类
        final Map<VersionType, List<E>> changeTypeMap = changedVersions.stream()
                .collect(Collectors.groupingBy(DataVersion::getVersionType));
        Map<LocalDate, VersionCalendar> result = new HashMap<>();
        // 1. 首先处理修改版本
        List<E> modifyVersions = changeTypeMap.get(VersionType.MODIFY);
        if (CollUtil.isNotEmpty(modifyVersions)) {
            ComputeResult<Map<LocalDate, VersionCalendar>, ErrorType> generateModifyMapResult = this.generateModifyCalendarMap(modifyVersions, versionChain);
            if (generateModifyMapResult.isSuccess()) {
                result.putAll(generateModifyMapResult.get());
            } else {
                return generateModifyMapResult.mapErr(t -> null);
            }
        }
        // 2. 处理新增版本
        final List<E> newVersions = changeTypeMap.get(VersionType.CREATE);
        // 3. 处理创建的版本，生成版本日历
        if (CollUtil.isNotEmpty(newVersions)) {
            if (newVersions.size() != 1) {
                return ComputeResult.fail(VersionErrorType.EXISTS_MULTI_GENESIS_VERSION);
            }
            final var newVersion = newVersions.getFirst();
            VersionCalendar newCalender = generateCreateVersionCalender(newVersion);
            result.put(newVersion.getVersionDate(), newCalender);
        }
        // 4. 处理删除的版本，生成版本日历
        final List<E> deletedVersions = changeTypeMap.get(VersionType.DELETE);
        if (CollUtil.isNotEmpty(deletedVersions)) {
            if (deletedVersions.size() != 1) {
                return ComputeResult.fail(VersionErrorType.EXISTS_MULTI_DELETE_VERSION);
            }
            final var deletedVersion = deletedVersions.getFirst();
            VersionCalendar deleteCalender = generateDeleteVersionCalender(deletedVersion);
            result.put(deletedVersion.getVersionDate(), deleteCalender);
        }
        return ComputeResult.success(result);
    }

    private <T, E extends DataVersion<T>> ComputeResult<Map<LocalDate, VersionCalendar>, ErrorType> generateModifyCalendarMap(List<E> modifyVersions, DataVersionChain<T, E> versionChain) {
        Map<LocalDate, Tuple<BusinessModel, BusinessModel>> comparePairMap = this.collectCompareVersionPairMap(modifyVersions, versionChain);
        Map<LocalDate, VersionCalendar> calendarMap = new HashMap<>(comparePairMap.size());
        for (Map.Entry<LocalDate, Tuple<BusinessModel, BusinessModel>> entry : comparePairMap.entrySet()) {
            LocalDate modifyDate = entry.getKey();
            Tuple<BusinessModel, BusinessModel> comparePair = entry.getValue();
            try {
                Optional<VersionCalendar> versionCalendarOpt = parseDiffToCalendar(versionChain.getDataId(), comparePair, modifyDate, versionChain.getBusinessModelClass());
                versionCalendarOpt.ifPresent(versionCalendar -> {
                    calendarMap.put(modifyDate, versionCalendar);
                });
            } catch (Exception e) {
                return ComputeResult.fail(VersionErrorType.GENERATE_MODIFY_CALENDER_ERROR, e);
            }
        }
        return ComputeResult.success(calendarMap);
    }

    private <T> Optional<VersionCalendar> parseDiffToCalendar(T dataId, Tuple<BusinessModel, BusinessModel> comparePair, LocalDate modifyDate, Class<? extends BusinessModel> businessModelClass) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        final Diff diff = CompareUtil.compare(comparePair);
        final var before = comparePair.getValue1();
        final var after = comparePair.getValue1();
        Long versionCalenderId = IdUtil.snowId();
        if (!diff.hasChanges()) {
            if (versionStrategyProvider.isEnableAutoMergeSameVersion()) {
                return Optional.empty();
            } else {
                return Optional.of(buildNoChangeCalendar(dataId, modifyDate, versionCalenderId, after, before));
            }
        }
        // 解析BusinessModel类
        final Map<String, VersionModel> businessFieldVersionModelMap = BusinessModelParser.parseIfAbsent(businessModelClass);
        // 解析Diff
        List<FieldVersionCalendar> fieldVersionCalenders = new ArrayList<>();
        for (Change change : diff.getChanges()) {
            if (change instanceof ValueChange valueChange) {
                String propertyName = valueChange.getPropertyName();
                final var versionModel = businessFieldVersionModelMap.get(propertyName);
                if (Objects.nonNull(versionModel) && versionModel instanceof FieldVersionModel fieldVersionModel) {
                    final var beforeValue = valueChange.getLeft();
                    final var afterValue = valueChange.getRight();
                    MessageProvider messageProvider = fieldVersionModel.getMessageProvider();
                    if (Objects.nonNull(messageProvider)) {
                        fieldVersionCalenders.add(
                                FieldVersionCalendar.builder()
                                        .fieldVersionId(IdUtil.snowId())
                                        .versionCalenderId(versionCalenderId)
                                        .fieldName(fieldVersionModel.getFieldName())
                                        .fieldClass(fieldVersionModel.getFieldClass())
                                        .fullRecordMsg(messageProvider.getFullMsg(beforeValue, afterValue))
                                        .conciseRecordMsg(messageProvider.getConciseMsg(beforeValue, afterValue))
                                        .beforeValue(beforeValue)
                                        .afterValue(afterValue)
                                        .showOrder(fieldVersionModel.getShowOrder())
                                        .build()
                        );
                        continue;
                    }
                    String showName = getFieldShowName(fieldVersionModel);
                    Tuple<String, String> valueStrTuple = getValueStrTuple(fieldVersionModel, beforeValue, afterValue);
                    fieldVersionCalenders.add(
                            FieldVersionCalendar.builder()
                                    .fieldVersionId(IdUtil.snowId())
                                    .versionCalenderId(versionCalenderId)
                                    .fieldName(fieldVersionModel.getFieldName())
                                    .fieldClass(fieldVersionModel.getFieldClass())
                                    .fullRecordMsg(formatRecordMessage(versionStrategyProvider.getFullRecordMessageFormat(), showName, valueStrTuple.getValue1(), valueStrTuple.getValue2()))
                                    .conciseRecordMsg(formatRecordMessage(versionStrategyProvider.getConciseRecordMessageFormat(), showName, valueStrTuple.getValue1(), valueStrTuple.getValue2()))
                                    .beforeValue(beforeValue)
                                    .afterValue(afterValue)
                                    .showOrder(fieldVersionModel.getShowOrder())
                                    .build()
                    );
                }
            } else if (change instanceof ListChange listChange) {
                final String propertyName = listChange.getPropertyName();
                VersionModel versionModel = businessFieldVersionModelMap.get(propertyName);
                if (Objects.nonNull(versionModel) && versionModel instanceof ListVersionModel listVersionModel) {
                    final List<?> beforeList = listChange.getLeft();
                    final List<?> afterList = listChange.getRight();
                    fieldVersionCalenders.addAll(compareOrderListField(new ArrayList<>(beforeList), new ArrayList<>(afterList), listVersionModel));
                }
            }
        }
        if (CollectionUtils.isEmpty(fieldVersionCalenders)) {
            if (versionStrategyProvider.isEnableAutoMergeSameVersion()) {
                return Optional.empty();
            } else {
                return Optional.of(buildNoChangeCalendar(dataId, modifyDate, versionCalenderId, after, before));
            }
        }
        return Optional.of(
                VersionCalendar.<T>builder()
                        .versionCalendarId(versionCalenderId)
                        .dataId(dataId.toString())
                        .dataName(after.getDataName())
                        .dataType(after.getDataType())
                        .versionType(VersionType.MODIFY)
                        .fieldVersionCalendars(fieldVersionCalenders)
                        .beforeJson(JsonUtil.toJson(before).orElse(null))
                        .afterJson(JsonUtil.toJson(after).orElse(null))
                        .dataVersionDate(modifyDate)
                        .build()
        );
    }

    private List<FieldVersionCalendar> compareOrderListField(List<Object> beforeList, List<Object> afterList, ListVersionModel versionModel) {
        if (CollectionUtils.isEmpty(beforeList) && CollectionUtils.isEmpty(afterList)) {
            return List.of();
        }
        // 获取配置项：是否合并展示
        boolean isMergeSameListChange = versionStrategyProvider.isMergeSameTypeListElementChange();
        if (CollUtil.isEmpty(beforeList)) {
            return buildAddElementFieldCalendarList(afterList, versionModel, isMergeSameListChange);
        }
        if (CollUtil.isEmpty(afterList)) {
            return buildRemoveElementFieldCalendarList(beforeList, versionModel, isMergeSameListChange);
        }
        // 两个list都不为空
        List<FieldVersionCalendar> result = new ArrayList<>();
        List<Object> addedElements = getListDiff(afterList, beforeList);
        List<Object> removedElements = getListDiff(beforeList, afterList);
        if (!CollectionUtils.isEmpty(addedElements)) {
            result.addAll(buildAddElementFieldCalendarList(addedElements, versionModel, isMergeSameListChange));
        }
        if (!CollectionUtils.isEmpty(removedElements)) {
            result.addAll(buildRemoveElementFieldCalendarList(removedElements, versionModel, isMergeSameListChange));
        }
        if (detectListOrderIsMoved(beforeList, afterList)) {
            result.add(
                    FieldVersionCalendar.builder()
                            .fieldVersionId(IdUtil.snowId())
                            .fieldName(versionModel.getFieldName())
                            .fullRecordMsg(versionModel.getOrderChangeMessage())
                            .conciseRecordMsg(versionModel.getOrderChangeMessage())
                            .fieldClass(versionModel.getFieldClass())
                            .showOrder(versionModel.getShowOrder())
                            .build()
            );
        }
        return result;
    }

    private List<FieldVersionCalendar> buildRemoveElementFieldCalendarList(List<Object> beforeList, ListVersionModel versionModel, boolean isMergeSameListChange) {
        // 翻译，如果有的话
        if (Objects.nonNull(versionModel.getTranslator())) {
            beforeList.forEach(before -> versionModel.getTranslator().translate(before));
        }
        if (isMergeSameListChange) {
            // 合并
            String removeElementSeparatedStr = beforeList.stream().map(Object::toString).collect(Collectors.joining(versionModel.getSeparator()));
            return List.of(
                    FieldVersionCalendar.builder()
                            .fieldVersionId(IdUtil.snowId())
                            .fieldName(versionModel.getFieldName())
                            .fieldClass(versionModel.getFieldClass())
                            .fullRecordMsg(CharSequenceUtil.format("{}: {}", versionModel.getRemoveElementShowName(), removeElementSeparatedStr))
                            .conciseRecordMsg(CharSequenceUtil.format("{}: {}", versionModel.getRemoveElementShowName(), removeElementSeparatedStr))
                            .showOrder(versionModel.getShowOrder())
                            .build()
            );
        } else {
            // 分开
            return beforeList.stream().map(before ->
                    FieldVersionCalendar.builder()
                            .fieldVersionId(IdUtil.snowId())
                            .fieldName(versionModel.getFieldName())
                            .fieldClass(versionModel.getFieldClass())
                            .fullRecordMsg(CharSequenceUtil.format("{}: {}", versionModel.getRemoveElementShowName(), before.toString()))
                            .conciseRecordMsg(CharSequenceUtil.format("{}: {}", versionModel.getRemoveElementShowName(), before.toString()))
                            .showOrder(versionModel.getShowOrder())
                            .build()
            ).toList();
        }
    }

    private List<FieldVersionCalendar> buildAddElementFieldCalendarList(List<Object> afterList, ListVersionModel versionModel, boolean isMergeSameListChange) {
        // 翻译，如果有的话
        if (Objects.nonNull(versionModel.getTranslator())) {
            afterList.forEach(after -> versionModel.getTranslator().translate(after));
        }
        if (isMergeSameListChange) {
            // 合并
            String addElementSeparatedStr = afterList.stream().map(Object::toString).collect(Collectors.joining(versionModel.getSeparator()));
            return List.of(
                    FieldVersionCalendar.builder()
                            .fieldVersionId(IdUtil.snowId())
                            .fieldName(versionModel.getFieldName())
                            .fieldClass(versionModel.getFieldClass())
                            .fullRecordMsg(CharSequenceUtil.format("{}: {}", versionModel.getAddElementShowName(), addElementSeparatedStr))
                            .conciseRecordMsg(CharSequenceUtil.format("{}: {}", versionModel.getAddElementShowName(), addElementSeparatedStr))
                            .showOrder(versionModel.getShowOrder())
                            .build()
            );
        } else {
            // 分开
            return afterList.stream().map(after ->
                    FieldVersionCalendar.builder()
                            .fieldVersionId(IdUtil.snowId())
                            .fieldName(versionModel.getFieldName())
                            .fieldClass(versionModel.getFieldClass())
                            .fullRecordMsg(CharSequenceUtil.format("{}: {}", versionModel.getAddElementShowName(), after.toString()))
                            .conciseRecordMsg(CharSequenceUtil.format("{}: {}", versionModel.getAddElementShowName(), after.toString()))
                            .showOrder(versionModel.getShowOrder())
                            .build()
            ).toList();
        }
    }

    private Tuple<String, String> getValueStrTuple(FieldVersionModel fieldVersionModel, Object beforeValue, Object afterValue) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        final ValueTranslator valueTranslator = fieldVersionModel.getValueTranslator();
        if (Objects.nonNull(valueTranslator)) {
            final var translatedBeforeValue = valueTranslator.translate(beforeValue);
            final var translatedAfterValue = valueTranslator.translate(afterValue);
            return Tuple.of(translatedBeforeValue, translatedAfterValue);
        }
        String nullValueShowStr = versionStrategyProvider.getNullValueShowStr();
        // 不使用值转换器，处理null值
        String beforeStr = nullValueShowStr;
        if (Objects.nonNull(beforeValue) && !(beforeValue instanceof CharSequence charSequence && StringUtils.isBlank(charSequence))) {
            beforeStr = beforeValue.toString();
        }
        String afterStr = nullValueShowStr;
        if (Objects.nonNull(afterValue) && !(afterValue instanceof CharSequence charSequence && StringUtils.isBlank(charSequence))) {
            afterStr = afterValue.toString();
        }
        return Tuple.of(beforeStr, afterStr);
    }

    private String getFieldShowName(FieldVersionModel fieldVersionModel) {
        // 优先使用静态名称
        if (StringUtils.isNotBlank(fieldVersionModel.getName())) {
            return fieldVersionModel.getName();
        }
        // 寻找动态名称

        final DynNameProvider dynFieldNameProvider = fieldVersionModel.getDynNameProvider();
        if (Objects.nonNull(dynFieldNameProvider)) {
            return dynFieldNameProvider.getDynFieldName(fieldVersionModel.getFieldName());
        }
        // 都没有，只能用属性名称了
        return fieldVersionModel.getFieldName();
    }

    private <T, E extends DataVersion<T>> Map<LocalDate, Tuple<BusinessModel, BusinessModel>> collectCompareVersionPairMap(List<E> modifyVersions, DataVersionChain<T, E> versionChain) {
        Map<LocalDate, Tuple<BusinessModel, BusinessModel>> distinctChangedPairMap = new HashMap<>();
        for (E editVersion : modifyVersions) {
            versionChain.findPrecedingVersion(editVersion).ifPresent(precedingVersion -> {
                distinctChangedPairMap.putIfAbsent(editVersion.getVersionDate(), Tuple.of(precedingVersion.toBusinessModel(), editVersion.toBusinessModel()));
            });
            versionChain.findFollowingVersion(editVersion).ifPresent(followingVersion -> {
                if (!followingVersion.isDeleted()) {
                    distinctChangedPairMap.putIfAbsent(followingVersion.getVersionDate(), Tuple.of(editVersion.toBusinessModel(), followingVersion.toBusinessModel()));
                }
            });
        }
        return distinctChangedPairMap;
    }

}
